/*
 * Copyright (C) 2010 Graham Allan
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.strath.cis.grallan.findbugs.adaptiveranking;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

import edu.strath.cis.grallan.findbugs.adaptiveranking.population.PopulationExtractor;
import edu.strath.cis.grallan.findbugs.adaptiveranking.util.RankingProgressNotifier;
import edu.strath.cis.grallan.findbugs.adaptiveranking.util.RankingStateNotifier;
import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.BugDesignation;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.gui2.IApplicationState;

/**
 * @author Graham Allan (grallan@cis.strath.ac.uk)
 */
public abstract class AbstractAdaptiveRanker<R extends AdaptiveRankBug<?>> implements AdaptiveRanker<R> {

	protected boolean rankingComputed = false;
	protected boolean canRefresh = false;

	protected BugCollection bugCollection;
	protected AdaptiveRankedBugList<R> rankedList;
	protected Map<BugInstance, R> bugToLearningRankMap = new HashMap<BugInstance, R>();

	protected final RankingStateNotifier stateNotifier = new RankingStateNotifier();
	protected final RankingProgressNotifier progressNotifier = new RankingProgressNotifier();
	protected final IApplicationState applicationState;
	protected final PopulationExtractor populationExtractorPrototype;

	public AbstractAdaptiveRanker(IApplicationState applicationState, PopulationExtractor populationExtractorPrototype) {
		super();
		this.applicationState = applicationState;
		this.populationExtractorPrototype = populationExtractorPrototype;
		this.bugCollection = populationExtractorPrototype.bugCollection();
	}

	public R getRankFor(BugInstance bug) {
		throwExceptionIfRankingHasNotBeenComputed();
		return bugToLearningRankMap.get(bug);
	}

	public boolean canCompute() {
		return bugCollection != null && bugCollection.getCollection().size() > 0;
	}

	public boolean canRefresh() {
		return canRefresh;
	}

	public boolean rankingComputed() {
		return rankingComputed;
	}

	public boolean addStateListener(RankingStateListener toAdd) {
		return stateNotifier.addStateListener(toAdd);
	}

	public boolean removeStateListener(RankingStateListener toRemove) {
		return stateNotifier.removeStateListener(toRemove);
	}

	public boolean addRankProgressCallback(RankingProgressCallback toAdd) {
		return progressNotifier.addRankProgressCallback(toAdd);
	}

	public boolean removeRankProgressCallback(RankingProgressCallback toRemove) {
		return progressNotifier.removeRankProgressCallback(toRemove);
	}

	public void setBugCollection(BugCollection newBugCollection) {
		boolean shouldReset = isDifferentBugCollection(newBugCollection);
		if (shouldReset) {
			reset();
			if (newBugCollection != null) {
				cloneBugCollection(newBugCollection);
			} else {
				this.bugCollection = null;
			}
			stateNotifier.doNotifyBugCollectionSet();
		}
	}

	private void cloneBugCollection(BugCollection newBugCollection) {
		this.bugCollection = newBugCollection.createEmptyCollectionWithMetadata();
		for (BugInstance bug : newBugCollection) {
			this.bugCollection.add(bug, false);
		}
	}

	private boolean isDifferentBugCollection(BugCollection bugCollection) {
		if (bugCollection == null)
			return true;
		if (this.bugCollection == null)
			return true;

		Collection<BugInstance> thatCollection = bugCollection.getCollection();
		Collection<BugInstance> thisCollection = this.bugCollection.getCollection();
		if (!thatCollection.equals(thisCollection))
			return true;

		return false;
	}

	protected void throwExceptionIfRankingHasNotBeenComputed() {
		if (!rankingComputed) {
			throw new IllegalStateException("This operation should not be called until the ranking is computed.");
		}
	}

	public void reset() {
		rankingComputed = false;
		canRefresh = false;
		stateNotifier.doNotifyReset();
	}

	public void notifyBugDesignated(BugInstance designated) {
		canRefresh = true;
		stateNotifier.doNotifyCanRefresh();
	}

	public void notifyAnalysisChanged() {
		setBugCollection(applicationState.bugCollection());
	}

	protected void assignBugRank(R bugRank) {
		rankedList.add(bugRank);
		bugToLearningRankMap.put(bugRank.bug(), bugRank);
	}

	public AdaptiveRankedBugList<R> getRankedList() {
		throwExceptionIfRankingHasNotBeenComputed();
		return rankedList;
	}

	protected void sortRankedList() {
		Collections.sort(rankedList, rankingComparator());
		Collections.reverse(rankedList);
	}

	protected abstract Comparator<? super R> rankingComparator();

	protected abstract double relativePositionOf(R bugRank);

	public double relativeRankFor(BugInstance bug) {
		throwExceptionIfRankingHasNotBeenComputed();

		R bugRank = getRankFor(bug);
		double relativeRank = bugRank == null ? defaultRelativeRank() : relativePositionOf(bugRank);
		return relativeRank;
	}

	protected abstract void assignRankForBug(PopulationExtractor extractor, BugInstance bug);

	protected abstract void createRankScheme();

	/**
	 * Template method for computing ranking order. Iterates over all bugs and defers assigning a
	 * rank for each bug to subclasses. Performs notifications if requested.
	 * 
	 * @param doNotifications
	 */
	protected void doComputeRankOrder(boolean doNotifications) {
		PopulationExtractor extractor = populationExtractorPrototype.cloneWith(bugCollection);
		int numberOfBugs = bugCollection.getCollection().size();

		if (doNotifications)
			progressNotifier.doNotifyComputationStarted(numberOfBugs);

		rankedList = new AdaptiveRankedBugList<R>();
		bugToLearningRankMap = new HashMap<BugInstance, R>();

		for (BugInstance bug : extractor.bugCollection()) {

			assignRankForBug(extractor, bug);

			if (doNotifications)
				progressNotifier.doNotifyValueComputed();
		}

		sortRankedList();
		createRankScheme();

		rankingComputed = true;
		canRefresh = false;

		if (doNotifications)
			progressNotifier.doNotifyRankingComputed();
	}

	public void refresh() {
		doComputeRankOrder(true);
		progressNotifier.doNotifyRefreshCompleted();
	}

	public void computeRankOrder() {
		doComputeRankOrder(true);
	}

	protected boolean bugAlreadyClassified(BugInstance bug) {
		return !BugDesignation.UNCLASSIFIED.equalsIgnoreCase(bug.getUserDesignationKey());
	}

}